package com.crunch.core;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class GameState {
	private Engine engine;
	public Engine getEngine() {
		return engine;
	}

	int updatePriority;
	public int getUpdatePriority() {
		return updatePriority;
	}

	private boolean shouldDestroy = false;
	public final boolean getShouldDestroy() {
		return shouldDestroy;
	}

	private boolean active;
	public final boolean getActive() {
		return active;
	}

	private boolean shouldBeActive;
	public final boolean getShouldBeActive() {
		return shouldBeActive;
	}

	private ArrayList<Actor> actors = new ArrayList<Actor>();
	private ArrayList<Actor> nonUpdatingActors = new ArrayList<Actor>();
	private ArrayList<Actor> newActors = new ArrayList<Actor>();

	Comparator<Actor> actorOrderer = new Comparator<Actor>() {
		@Override
		public int compare(Actor o1, Actor o2) {
			if (o1.getUpdatePriority() < o2.getUpdatePriority()) {
				return -1;
			} else if (o1.getUpdatePriority() > o2.getUpdatePriority()) {
				return 1;
			} else {
				return 0;
			}
		}
	};


	public GameState(int updatePriority) {
		this.updatePriority = updatePriority;
	}

	// called by engine - don't call this manually
	final void initialize(Engine engine, boolean initiallyActive) {
		this.engine = engine;
		active = initiallyActive;
		shouldBeActive = initiallyActive;
	}

	// called by engine - don't call this manually
	final void terminate() {
		// we destroy the actors here
		// keep destroying until all are gone (possible that destroying creates new actors)
		while (!actors.isEmpty()) {
			actors.get(actors.size()-1).destroy();
		}
		while (!nonUpdatingActors.isEmpty()) {
			nonUpdatingActors.get(nonUpdatingActors.size()-1).destroy();
		}
	}

	private int updateIndex;
	private Actor actorBeingUpdated = null;
	// called by engine - don't call this manually
	final void update() {
		// we do this at the beginning so that actors created in onCreate() events will immediately update
		for (Actor a : newActors) {
			if (a.isInUpdateQueue()) {
				actors.add(a);
			} else {
				nonUpdatingActors.add(a);
			}
		}
		newActors.clear();

		// we update the actors here
		// make sure they are in proper update order
		Collections.sort(actors, actorOrderer);

		// do an explicit for loop in order to properly handle removing actors
		for (updateIndex = 0; updateIndex < actors.size(); ++updateIndex) {
			actorBeingUpdated = actors.get(updateIndex);
			actorBeingUpdated.onUpdate();
		}
		actorBeingUpdated = null;

		for (Actor a : newActors) {
			if (a.isInUpdateQueue()) {
				actors.add(a);
			} else {
				nonUpdatingActors.add(a);
			}
		}
		newActors.clear();
	}

	// marks this game state to be destroyed at the beginning of the next update
	public final void destroy() {
		shouldDestroy = true;
	}

	// sets this game state's active status at the beginning of the next update
	public final void setActive(boolean active) {
		shouldBeActive = active;
	}

	// called by engine - don't call this manually
	final void setActiveFlag(boolean active) {
		this.active = active;
	}

	public final <T extends Actor> T createActor(Class<T> actorClass) {
		T a;
		try {
			a = actorClass.newInstance();
		} catch (InstantiationException e) {
			System.out.println("Error: failed to instantiate class " + actorClass.getSimpleName());
			return null;
		} catch (IllegalAccessException e) {
			System.out.println("Error: failed to instantiate class " + actorClass.getSimpleName());
			return null;
		}
		a.initialize(this);
		newActors.add(a);
		a.onCreate();

		return a;
	}

	public final void destroyActor(Actor actor) {
		if (this != actor.getGameState()) {
			throw new IllegalStateException("Attempted to destroy actor from the wrong game state");
		}

		if (!actor.exists()) {
			throw new IllegalStateException("Attempted to destroy non-existent actor");
		}

		// flag actor as not existing - this way destroy() won't be called twice and cause issues
		actor.setDoesNotExist();

		actor.onDestroy();
		// handle position in update list
		if (actor == actorBeingUpdated) {
			--updateIndex;
		}
		// first try to destroy the actor from the main list
		if (!(actor.isInUpdateQueue() ? actors : nonUpdatingActors).remove(actor)) {
			// if that fails, check the new actor list
			newActors.remove(actor);
		}
	}

	// override these methods where necessary

	public void onCreate(Object... args) {} // called right after the game state is created
	public void onUpdate() {}               // called at each update
	public void onDestroy() {}              // called right before the game state is destroyed
	public void onActivate() {}             // called right after the game state is activated
	public void onDeactivate() {}           // called right after the game state is deactivated
}
